﻿using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Globalization;
using System.Collections;
using System.Reflection;

#if DEBUG
using System.Diagnostics;
#endif

namespace Sencha.Serialization
{
	public static class Json
	{
		private static IFormatProvider s_DefaultFormatProvider = CultureInfo.InvariantCulture;
		private static Encoding s_DefaultEncoding = new System.Text.UTF8Encoding(false, true);		
		private static string[] s_DateTimeFormats = new string[] { "yyyy-MM-ddTHH:mm:ss.fffzzz", // ISO 8601, with timezone
																"yyyy-MM-ddTHH:mm:ss.fffZ" }; // also ISO 8601, widthout timezone
#if NET40
		public static Type DefaultDictionaryType = typeof(System.Dynamic.ExpandoObject);
#else
		public static Type DefaultDictionaryType = typeof(Dictionary<string, object>);
#endif

		public static string[] DateTimeFormats
		{
			get
			{
				return s_DateTimeFormats;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");
				if (value.Length == 0)
					throw new ArgumentException();

				System.Diagnostics.Contracts.Contract.ForAll(value, v => value != null);
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				s_DateTimeFormats = value;
			}
		}
		public static IFormatProvider DefaultFormatProvider
		{
			get { return s_DefaultFormatProvider; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				s_DefaultFormatProvider = value;
			}
		}
		public static Encoding DefaultEncoding
		{
			get { return s_DefaultEncoding; }
#if !STRICT
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");
				System.Diagnostics.Contracts.Contract.EndContractBlock();

				s_DefaultEncoding = value;
			}
#endif
		}

		public static void Serialize(object objectToSerialize, Stream jsonOutput)
		{
			if (jsonOutput == null)
				throw new ArgumentNullException("jsonOutput");
			if (!jsonOutput.CanWrite)
				throw new Exceptions.UnwriteableStream("jsonOutput");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (objectToSerialize == null)
			{
				var bytes = DefaultEncoding.GetBytes("null");
				jsonOutput.Write(bytes, 0, bytes.Length);
				return;
			}

			var serializer = new JsonSerializer(objectToSerialize.GetType());
			serializer.WriteObject(jsonOutput, objectToSerialize);
		}
		public static string SerializeToString(object objectToSerialize)
		{
			if (objectToSerialize == null)		
				return "null";			

			var serializer = new JsonSerializer(objectToSerialize.GetType());
			var writer = new StringWriter();
			serializer.WriteObject(writer, objectToSerialize);
			return writer.ToString();
		}
		public static object Deserialize(Type objectType, Stream jsonStream)
		{
			if (objectType == null)
				throw new ArgumentNullException("objectType");
			if (jsonStream == null)
				throw new ArgumentNullException("jsonStream");
			if (!jsonStream.CanRead)
				throw new Exceptions.UnreadableStream("jsonStream");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var serializer = new JsonSerializer(objectType);
			return serializer.ReadObject(jsonStream);
		}
		public static object Deserialize(Type objectType, string jsonString)
		{
			if (objectType == null)
				throw new ArgumentNullException("objectType");
			if (jsonString == null)
				throw new ArgumentNullException("jsonString");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var serializer = new JsonSerializer(objectType);
			var reader = new StringReader(jsonString);
			return serializer.ReadObject(jsonString);
		}

		public static void Serialize<T>(T objectToSerialize, Stream jsonOutput)
		{
			if (jsonOutput == null)
				throw new ArgumentNullException("jsonOutput");
			if (!jsonOutput.CanWrite)
				throw new Exceptions.UnwriteableStream("jsonOutput");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (objectToSerialize == null)
			{
				var bytes = DefaultEncoding.GetBytes("null");
				jsonOutput.Write(bytes, 0, bytes.Length);
				return;
			}

			var serializer = new JsonSerializer(typeof(T));
			serializer.WriteObject<T>(jsonOutput, objectToSerialize);
		}
		public static string SerializeToString<T>(T objectToSerialize)
		{
			var serializer = new JsonSerializer(typeof(T));
			var writer = new StringWriter();
			serializer.WriteObject<T>(writer, objectToSerialize);

			return writer.ToString();
		}
		public static object Deserialize<T>(Stream jsonStream)
		{
			if (jsonStream == null)
				throw new ArgumentNullException("jsonStream");
			if (!jsonStream.CanRead)
				throw new Exceptions.UnreadableStream("jsonStream");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var serializer = new JsonSerializer(typeof(T));
			return serializer.ReadObject<T>(jsonStream);
		}
		public static object Deserialize<T>(string jsonString)
		{
			if (jsonString == null)
				throw new ArgumentNullException("jsonString");
			if (string.IsNullOrEmpty(jsonString))
				throw new ArgumentNullOrEmptyException("jsonString");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (jsonString == "null")
				return null;

			var serializer = new JsonSerializer(typeof(T));
			var reader = new StringReader(jsonString);
			return serializer.ReadObject<T>(reader);
		}
	}
}
